__metaclass__ = type

import os
from group import group
from proxy import proxy
from noop import noop, reset

DEBUG = os.getenv('DEBUG_MATCH')

class expr:

    def __init__(self, comps=None, func=noop, pre=reset, post=noop):
        if comps is None:
            comps = []
        self._sub = comps
        self.func = func
        self.code = []
        self.pre = pre
        self.post = post
        self.children = []

    def __getattribute__(self, attr):
        if attr in ['__repr__', '__call__', '_sub', '__iter__',
                    'func', 'children', 'pre', 'post', 'code']:
            return object.__getattribute__(self, attr)
        return object.__getattribute__(self._sub, attr)

    def __add__(self, other):
        return tuple(self) + (other,)

    def __len__(self):
        return sum(map(len, self._sub))

    def __iter__(self):
        return iter(self._sub)

    def __repr__(self):
        items = map(str, self._sub)
        return ' '.join(items)

    def __call__(self, items):
        self.match = []
        e_stack = list(self._sub)
        orig_items = list(items)
        self.pre(orig_items, self)
        items = list(items)
        if items and type(items[0]) is type(1):
            items = [items]
        items.reverse()
        e_stack.reverse()
        while e_stack:
            e = e_stack.pop()
            while items:
                is_expr = (isinstance(e, expr) or isinstance(e, group)
                           or isinstance(e, proxy))
                if is_expr:
                    cur = list(items)
                    cur.reverse()
                else:
                    cur = items.pop()
                    e.pre([cur], e)
                if DEBUG:
                    print 'trying', e, cur
                match = e(cur)
                if not match:
                    if DEBUG:
                        print 'no match', e, cur
                    if e.required:
                        return False
                    if not is_expr:
                        items.append(cur)
                    break
                else:
                    if DEBUG:
                        print 'match', e, cur
                    if is_expr:
                        eat = len(cur)
                        if match is not True:
                            eat = len(items) - len(match)
                        tokens = items[-eat:]
                        tokens.reverse()
                        self.children.extend(e.func(tokens, e))
                        self.code.extend(e.code)
                        items = items[:-eat]
                    else:
                        self.children.extend(e.func([cur], e))
                        e.post([cur], e)
                        self.code.extend(e.code)
                    if e.single:
                        break
                    if not e.single and e.required:
                        # oneOrMore
                        e = e.copy(required=False)
            if not e.single and e.required and items:
                e_stack.append(e)
        if not e_stack or not filter(lambda x: not x.required, e_stack):
            items.reverse()
            self.post(orig_items, self)
            return items or True
        return False
